// See LICENSE.SiFive for license details.

package uncore.tilelink2

import Chisel._
import chisel3.internal.sourceinfo.SourceInfo
import config._
import diplomacy._
import uncore.ahb._
import scala.math.{min, max}
import AHBParameters._

case class TLToAHBNode() extends MixedNode(TLImp, AHBImp)(
  dFn = { case (1, Seq(TLClientPortParameters(clients, unsafeAtomics, minLatency))) =>
    val masters = clients.map { case c => AHBMasterParameters(nodePath = c.nodePath) }
    Seq(AHBMasterPortParameters(masters))
  },
  uFn = { case (1, Seq(AHBSlavePortParameters(slaves, beatBytes))) =>
    val managers = slaves.map { case s =>
      TLManagerParameters(
        address            = s.address,
        regionType         = s.regionType,
        executable         = s.executable,
        nodePath           = s.nodePath,
        supportsGet        = s.supportsRead,
        supportsPutFull    = s.supportsWrite, // but not PutPartial
        fifoId             = Some(0)) // a common FIFO domain
    }
    Seq(TLManagerPortParameters(managers, beatBytes, 1, 1))
  },
  numPO = 1 to 1,
  numPI = 1 to 1)

class TLToAHB(combinational: Boolean = true)(implicit p: Parameters) extends LazyModule
{
  val node = TLToAHBNode()

  lazy val module = new LazyModuleImp(this) {
    val io = new Bundle {
      val in = node.bundleIn
      val out = node.bundleOut
    }

    val in = io.in(0)
    val out = io.out(0)
    val edgeIn  = node.edgesIn(0)
    val edgeOut = node.edgesOut(0)
    val beatBytes = edgeOut.slave.beatBytes
    val maxTransfer = edgeOut.slave.maxTransfer
    val lgMax = log2Ceil(maxTransfer)
    val lgBytes = log2Ceil(beatBytes)

    // AHB has no cache coherence
    in.b.valid := Bool(false)
    in.c.ready := Bool(true)
    in.e.ready := Bool(true)

    // We need a skidpad to capture D output:
    // We cannot know if the D response will be accepted until we have
    // presented it on D as valid.  We also can't back-pressure AHB in the
    // data phase.  Therefore, we must have enough space to save the data
    // phase result.  Whenever we have a queued response, we can not allow
    // AHB to present new responses, so we must quash the address phase.
    val d = Wire(in.d)
    in.d <> Queue(d, 1, flow = true)
    val a_quash = in.d.valid && !in.d.ready

    // Record what is coming out in d_phase
    val d_valid   = RegInit(Bool(false))
    val d_hasData = Reg(Bool())
    val d_error   = Reg(Bool())
    val d_addr_lo = Reg(UInt(width = lgBytes))
    val d_source  = Reg(UInt())
    val d_size    = Reg(UInt())

    when (out.hreadyout) { d_error := d_error || out.hresp }
    when (d.fire()) { d_valid := Bool(false) }

    d.valid := d_valid && out.hreadyout
    d.bits  := edgeIn.AccessAck(d_addr_lo, UInt(0), d_source, d_size, out.hrdata, out.hresp || d_error)
    d.bits.opcode := Mux(d_hasData, TLMessages.AccessAckData, TLMessages.AccessAck)

    // We need an irrevocable input for AHB to stall on read bursts
    // We also need the values to NOT change when valid goes low => 1 entry only
    val a = Queue(in.a, 1, flow = combinational, pipe = !combinational)
    val a_valid = a.valid && !a_quash

    // This is lot like TLEdge.firstlast, but counts beats also for single-beat TL types
    val a_size = edgeIn.size(a.bits)
    val a_beats1 = UIntToOH1(a_size, lgMax) >> lgBytes
    val a_counter = RegInit(UInt(0, width = log2Up(maxTransfer/beatBytes)))
    val a_counter1 = a_counter - UInt(1)
    val a_first = a_counter === UInt(0)
    val a_last = a_counter === UInt(1) || a_beats1 === UInt(0)
    val a_offset = (a_beats1 & ~a_counter1) << lgBytes
    val a_hasData = edgeIn.hasData(a.bits)

    // Expand no-data A-channel requests into multiple beats
    a.ready := (a_hasData || a_last) && out.hreadyout && !a_quash
    when (a_valid && out.hreadyout) {
      a_counter := Mux(a_first, a_beats1, a_counter1)
      d_valid := !a_hasData || a_last
      // Record what will be in the data phase
      when (a_first) {
        d_hasData := !a_hasData
        d_error   := Bool(false)
        d_addr_lo := a.bits.address
        d_source  := a.bits.source
        d_size    := a.bits.size
      }
    }

    // Transform TL size into AHB hsize+hburst
    val a_size_bits = a_size.getWidth
    val a_sizeDelta = Cat(UInt(0, width = 1), a_size) - UInt(lgBytes+1)
    val a_singleBeat = a_sizeDelta(a_size_bits)
    val a_logBeats1 = a_sizeDelta(a_size_bits-1, 0)

    out.hmastlock := Bool(false) // for now
    out.htrans    := Mux(a_valid, Mux(a_first, TRANS_NONSEQ, TRANS_SEQ), Mux(a_first, TRANS_IDLE, TRANS_BUSY))
    out.hsel      := a_valid || !a_first
    out.hready    := out.hreadyout
    out.hwrite    := a_hasData
    out.haddr     := a.bits.address | a_offset
    out.hsize     := Mux(a_singleBeat, a.bits.size, UInt(lgBytes))
    out.hburst    := Mux(a_singleBeat, BURST_SINGLE, (a_logBeats1<<1) | UInt(1))
    out.hprot     := PROT_DEFAULT
    out.hwdata    := RegEnable(a.bits.data, a.fire())
  }
}

object TLToAHB
{
  // applied to the TL source node; y.node := TLToAHB()(x.node)
  def apply(combinational: Boolean = true)(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): AHBOutwardNode = {
    val ahb = LazyModule(new TLToAHB(combinational))
    ahb.node := x
    ahb.node
  }
}
